/**
 * 
 */
package uk.co.droidinactu.inlibrislibertas.model;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

import uk.co.droidinactu.common.file.AsciiFileReader;
import uk.co.droidinactu.inlibrislibertas.LibraryConstants;
import android.util.Log;

/**
 * @author andy
 */
public class CalibreMetadataReader extends AsciiFileReader {

	public static final String lineEndings = "[{,}]\"";

	private static String LOG_TAG = "InLibris_CalibreMetsDataRdr:";
	private ArrayList<String> bookAuthorList = new ArrayList<String>();

	private ArrayList<String> bookTagList = new ArrayList<String>();

	private final ImportProgressObject currProgressObj = new ImportProgressObject();

	private final CalibreDataModel dataMdl;
	private final int nbrBooksBetweenUiUpdates = 4;

	private final long sleepBetweenBookImports = 150;
	private EBook tmpBk;

	public CalibreMetadataReader(final CalibreDataModel dataModel) {
		dataMdl = dataModel;
		tmpBk = new EBook();
	}

	private String getBase64String(final String base64Strd) {
		String temp = base64Strd;

		if (temp.endsWith("=")) {
			temp = temp.substring(0, temp.length() - 1);
		}
		if (temp.endsWith("=")) {
			temp = temp.substring(0, temp.length() - 1);
		}

		int asciiChars = temp.length();// - temp.Count(c =>
		// Char.IsWhiteSpace(c));
		switch (asciiChars % 4) {
		case 1:
			// This would always produce an exception!!
			// Regardless what (or what not) you attach to your string!
			// Better would be some kind of throw new Exception()
			// return new byte[0];
		case 0:
			asciiChars = 0;
			break;
		case 2:
			asciiChars = 2;
			temp += "==";
			break;
		case 3:
			asciiChars = 1;
			temp += "=";
			break;
		}

		return temp;
	}

	private HashMap<String, String> getKeyValuePair(final String fileLine) {
		final HashMap<String, String> recordFields = new HashMap<String, String>();
		final StringTokenizer tk = new StringTokenizer(fileLine, ":");
		final String key = tk.nextToken();
		String value = tk.nextToken();
		while (tk.hasMoreTokens()) {
			value += ":" + tk.nextToken();
		}
		recordFields.put(key, value);
		return recordFields;
	}

	@Override
	public String NextLineFromFile() {
		String tmpStr = super.NextLineFromFile();
		if (!tmpStr.endsWith("null")) {
			while (!CalibreMetadataReader.lineEndings.contains(tmpStr.substring(tmpStr.length() - 1))) {
				tmpStr += "\\n" + super.NextLineFromFile();
			}
		}
		currProgressObj.currentBooksRead += tmpStr.length();
		return tmpStr;
	}

	public void readBooks() {

		final long fileSize = new File(m_sFilepath + getFilename()).length();
		currProgressObj.totalNbrBooks = new Long(fileSize).intValue();
		currProgressObj.currentBooksRead = 0;

		Log.d(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG, "reading calibre metadata from ["
				+ getFilename() + "]");

		String prevLine = "";
		String currLine = FirstLineFromFile().trim();
		currProgressObj.currentBooksRead += currLine.length();

		if (currLine.length() != 1 && !currLine.equals("[")) {
			// FIXME : File Format Error
			return;
		}
		prevLine = currLine;
		currLine = NextLineFromFile();
		int currentBkNbr = 0;

		while (currLine.length() > 0) {

			if (prevLine.equals("},") && currLine.equals("{") || prevLine.equals("}") && currLine.equals("]")) {
				final File f = new File(m_sFilepath + tmpBk.lpath);
				if (f.exists()) {
					try {
						tmpBk.setAuthors(bookAuthorList);
						tmpBk.setTags(bookTagList);
						tmpBk.setUniqueIdentifier(dataMdl.addBook(tmpBk));
						currentBkNbr++;

					} catch (final Exception e) {
						Log.e(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG,
								"exception adding book to database: " + e.getMessage(), e);
					}

					for (final String s : bookAuthorList) {
						try {
							dataMdl.addAuthorBookLink(s, tmpBk);
							// Thread.sleep(20);
						} catch (final Exception e) {
							Log.e(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG, "exception adding author ["
									+ s + "] to database: " + e.getMessage(), e);
						}
					}

					for (final String s : bookTagList) {
						try {
							dataMdl.addTagBookLink(s, tmpBk);
							// Thread.sleep(20);
						} catch (final Exception e) {
							Log.e(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG, "exception adding tag ["
									+ s + "] to database: " + e.getMessage(), e);
						}
					}

					bookAuthorList = new ArrayList<String>();
					bookTagList = new ArrayList<String>();
				}
				tmpBk = new EBook();
				prevLine = currLine;
				currLine = NextLineFromFile();
			} else {
				if (currLine.length() > 2) {
					try {
						readRecord(currLine);
					} catch (final Exception e) {
						Log.e(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG + ":readRecord",
								"error parsing line [" + currLine + "] " + e.getMessage(), e);
					}
				}
			}
			prevLine = currLine;
			currLine = NextLineFromFile();
		}
		dataMdl.informObserversViaHandler(currProgressObj);
	}

	private List<String> readList(String fileLine) {
		// Log.d(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG +
		// "readList()",
		// "started");
		final List<String> recordFields = new ArrayList<String>();

		if (fileLine.endsWith("[],") || fileLine.endsWith("{},")) {
			return recordFields;
		}

		while ((fileLine = NextLineFromFile()).length() > 2) {
			if (fileLine.startsWith("\"")) {
				fileLine = fileLine.substring(1, fileLine.length() - 1);
			}
			if (fileLine.endsWith("\"")) {
				fileLine = fileLine.substring(0, fileLine.length() - 1);
			}
			recordFields.add(fileLine);
		}

		return recordFields;
	}

	private HashMap<String, String> readListKeyValue(String fileLine) {
		// Log.d(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG +
		// "readListKeyValue()", "started");
		final HashMap<String, String> recordFields = new HashMap<String, String>();

		if (fileLine.endsWith("[],") || fileLine.endsWith("{},")) {
			return recordFields;
		}

		while ((fileLine = NextLineFromFile().trim()).length() > 2) {
			recordFields.putAll(getKeyValuePair(fileLine));
		}

		return recordFields;
	}

	private void readRecord(final String fileLine) {
		// Log.d(LibraryConstants.LOG_TAG + CalibreMetadataReader.LOG_TAG +
		// "readRecord()", "started");
		String key;
		final StringTokenizer tk = new StringTokenizer(fileLine, ":");
		key = tk.nextToken().trim().substring(1).trim();
		key = key.substring(0, key.length() - 1).trim();
		String value = tk.nextToken().trim();
		while (tk.hasMoreTokens()) {
			value += ":" + tk.nextToken();
		}

		if (value.endsWith(",")) {
			value = value.substring(0, value.length() - 1).trim();
		}
		if (value.startsWith("\"")) {
			value = value.substring(1, value.length() - 1).trim();
		}

		if (key.equals("application_id")) {
			try {
				tmpBk.application_id = Integer.parseInt(value);
			} catch (final Exception e) {
			}
		} else if (key.equals("author_sort")) {
			tmpBk.author_sort = value;
		} else if (key.equals("author_sort_map")) {
			tmpBk.author_sort_map = readListKeyValue(fileLine);
		} else if (key.equals("languages")) {
			tmpBk.languages = readList(fileLine);
		} else if (key.equals("classifiers")) {
			tmpBk.classifiers = readListKeyValue(fileLine);
		} else if (key.equals("cover_data")) {
			tmpBk.cover_data = readList(fileLine);
		} else if (key.equals("rating")) {
			try {
				tmpBk.rating = Integer.parseInt(value);
			} catch (final Exception e) {
			}
		} else if (key.equals("isbn")) {
			tmpBk.isbn = value;
		} else if (key.equals("pubdate")) {
			tmpBk.pubdate = value;
		} else if (key.equals("series")) {
			if (!value.equals("null")) {
				tmpBk.setSeries(value);
			}
		} else if (key.equals("timestamp")) {
			tmpBk.timestamp = value;
		} else if (key.equals("publication_type")) {
			tmpBk.publication_type = value;
		} else if (key.equals("size")) {
			try {
				tmpBk.setSize(Integer.parseInt(value));
			} catch (final Exception e) {
			}
		} else if (key.equals("category")) {
			tmpBk.category = value;
		} else if (key.equals("uuid")) {
			tmpBk.uuid = value;
		} else if (key.equals("title")) {
			tmpBk.title = value;
		} else if (key.equals("comments")) {
			tmpBk.comments = value;
		} else if (key.equals("ddc")) {
			tmpBk.ddc = value;
		} else if (key.equals("tags")) {
			for (final String s : readList(fileLine)) {
				bookTagList.add(s);
			}
		} else if (key.equals("mime")) {
			tmpBk.mime = value;
			if (value.equalsIgnoreCase("null")) {
				if (tmpBk.lpath.endsWith("epub")) {
					tmpBk.mime = MimeTypes.MIME_TYPE_EPUB;
				} else if (tmpBk.lpath.endsWith("pdf")) {
					tmpBk.mime = MimeTypes.MIME_TYPE_PDF;
				}
			}
		} else if (key.equals("thumbnail")) {
			if (!value.equalsIgnoreCase("null")) {
				final List<String> tmpList = readList(fileLine);
				tmpBk.thumbnail.height = Integer.parseInt(tmpList.get(0).substring(0, tmpList.get(0).length() - 1));
				tmpBk.thumbnail.width = Integer.parseInt(tmpList.get(1).substring(0, tmpList.get(0).length() - 1));
				tmpBk.thumbnail.data = getBase64String(tmpList.get(2).substring(0, tmpList.get(2).length() - 1));
			}
		} else if (key.equals("db_id")) {
			tmpBk.db_id = value;
		} else if (key.equals("user_metadata")) {
			tmpBk.user_metadata = readList(fileLine);
		} else if (key.equals("lcc")) {
			tmpBk.lcc = value;
		} else if (key.equals("authors")) {
			for (final String s : readList(fileLine)) {
				bookAuthorList.add(s);
			}
		} else if (key.equals("publisher")) {
			tmpBk.publisher = value;
		} else if (key.equals("series_index")) {
			tmpBk.setSeriesIndex(value);
		} else if (key.equals("lpath")) {
			tmpBk.lpath = value;
		} else if (key.equals("cover")) {
			tmpBk.cover = value;
		} else if (key.equals("language")) {
			tmpBk.language = value;
		} else if (key.equals("rights")) {
			tmpBk.rights = value;
		} else if (key.equals("lccn")) {
			tmpBk.lccn = value;
		} else if (key.equals("title_sort")) {
			tmpBk.title_sort = value;
		} else if (key.equals("book_producer")) {
			tmpBk.book_producer = value;
		} else {
			// FIXME unknown tag!!
		}
	}

}
